/*
 * This file is part of algowala.
 *
 * algowala is copyright (c) 2012 Jagannathan Sampath <algowala@jagernot.com>
 *
 * algowala is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * algowala is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with algowala.  If not, see <http://www.gnu.org/licenses/>.
 *
*/

#include "console.h"
#include "font.h"
#include "input.h"
#include "command.h"
#include "tcl_interp.h"
#include "tokenizer.h"
#include "key_consts.h"
#include "ansi_color_codes.h"

#include <algorithm>
#include <iostream>

using namespace std;

#define buf cmdlst.buf

extern cmdlist cmdlst;

extern tcl_interp* p_interp;

void console::add_line (const mesg& ln) {
  lines.push_back (ln);
  ++nlines;
  last ();
}

void console::up (int i) {
  startl = max (0, startl - i);
}

void console::down (int i) {
  startl += i;
  if (startl >= nlines) startl = max (0, nlines - 1);
}

void console::pgdn () {
  down (lines_per_screen);
}

void console::pgup () {
  up (lines_per_screen);
}

void console::home () {
  startl = 0;
}

void console::end () {
  startl = max (0, nlines - 1);
}

void console::last () {
  // ensures last line is always displayed
  end ();
  if (!rollup) up (lines_per_screen - 1);
}

console::console () : ps1 ("->") {
  rollup = 0;
  command_mode = 0;
  curs_loc = 0;
  curs_locx = 0;
  hid = -1;
  clear ();
  cout << DOING << "<<< initialising console, done >>>" << ENDL;
}

console& console::operator<< (const color& c) {
  clr = c;
  return *this;
}

console& console::operator<< (const string& s) {
  cur_line.text += s;
  return *this;
}

console& console::operator<< (unsigned int i) {
  sprintf (buf, "%x", i);
  *this << buf;
  return *this;
}

console& console::operator<< (int i) {
  sprintf (buf, "%d", i);
  *this << buf;
  return *this;
}

console& console::operator<< (unsigned long  i) {
  sprintf (buf, "%lu", i);
  *this << buf;
  return *this;
}

console& console::operator<< (unsigned long long i) {
  sprintf (buf, "%llux", i);
  *this << buf;
  return *this;
}

console& console::operator<< (float f) {
  sprintf (buf, "%f", f);
  *this << buf;
  return *this;
}

console& console::operator<< (double d) {
  sprintf (buf, "%f", d);
  *this << buf;
  return *this;
}

console& console::operator<< (char c) {
  if (c == '\n') {
    cur_line.clr = clr;
    add_line (cur_line);
    cur_line = mesg ();
  } else cur_line.text += c;
  return *this;
}

void console::del () {
  int len = cmd_line.text.length ();
  if (curs_loc > 0 && curs_loc <= len) {
    cmd_line.text.erase (cmd_line.text.begin() + curs_loc - 1);
    curs_locx = (int) get_char_width (cmd_line.text.substr (0, --curs_loc));
  }
}

void console::calc_visual_params () {
  line_height = get_line_height ();
  char_width = fnt.max_char_width;
  lines_per_screen = (win.top - win.bottom) / line_height - GUTTER;
  startx = win.left + get_char_width (ps1);
  starty = win.top - line_height;
}

void console::draw () {

  glMatrixMode (GL_MODELVIEW);

  glPushMatrix ();
  glLoadIdentity ();

  glMatrixMode (GL_PROJECTION);
  glPushMatrix ();
  glLoadIdentity ();
  glOrtho (win.left, win.right, win.bottom, win.top, -1, 1);

  // draw lines
  int cx = startx, cy = starty;

  int n = 0, i = startl;
  while ((n++ < lines_per_screen) && (i < nlines)) {
    const mesg& linei = lines[i++];
    const color& clr = linei.clr;
    glColor3f (clr.r, clr.g, clr.b);
    draw_string (linei.text, cx, cy);
    cy -= line_height;
  }

  // draw last line
  glColor3f (1, 1, 1);
  draw_string (ps1, win.left, cy);
  draw_string (cmd_line.text, cx, cy, 0);

  // draw cursor
  if (command_mode) {
    glColor3f (1, 0, 0);
    int px = cx + curs_locx, py = cy - 2;
    glBegin (GL_LINES);
      glVertex2i (px, py);
      glVertex2i (px + char_width, py);
    glEnd ();
  }
  
  glEnd ();
  glPopMatrix ();

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix ();


}

void console::clear () {
  lines.clear ();
  cmd_line = mesg();
  curs_loc = 0;
  curs_locx = 0;
  nlines = 0;
  startl = 0;
}

void console::set_window (const box<int>& w) {
  win = w;
  calc_visual_params ();
}

void accept_keypress (int accept);

int console::handle_input () {
	
	int ret = 0;
	
	extern char xkey;
  if (command_mode) {
		
		ret = 1;

    if ((xkey > 9) && (xkey < 127) && (xkey != 13)) {
      cmd_line.text.insert (cmd_line.text.begin() + curs_loc, xkey);
      ++curs_loc;
      curs_locx = get_char_width (cmd_line.text.substr (0, curs_loc));
    }
    xkey = 0;
		 
    if (keypressed (k_enter)) {

      add_line (cmd_line);

      history.push_back (cmd_line.text);
      hid = history.size () - 1;

      operator() (cmd_line.text);
      cmd_line = mesg ();
      curs_loc = curs_locx = 0;

    }  
    
    else if (keypressedd (k_left)) {
			
      if (--curs_loc < 1) 
				curs_loc = curs_locx = 0;
      else 
				curs_locx = (int) get_char_width (cmd_line.text.substr (0, curs_loc));
				
			
    } else if (keypressedd (k_right)) {
      
      int len = cmd_line.text.length ();
      if (++curs_loc > len) curs_loc = len;
      curs_locx = (int) get_char_width (cmd_line.text.substr (0, curs_loc));


    } 
        
    if (hid > -1) {

      if (keypressedd (k_up)) {
        cmd_line.text = history [hid--];
        curs_loc = cmd_line.text.length ();
        curs_locx = get_char_width (cmd_line.text);
        if (hid < 0) hid = history.size () - 1;
      }

      if (keypressedd (k_down)) {
        cmd_line.text = history [hid++];
        curs_loc = cmd_line.text.length ();
        curs_locx = get_char_width (cmd_line.text);
        if (hid >= (int) history.size()) hid = 0;
      }

    }

  }

  static double sc0 = 0.5, sc1 = 1./128;
  static double pg0 = 0.5, pg1 = 1./32;

  if (keypressed (k_tab) & !(keydown (k_lalt) || keydown (k_ralt)) ) {
		toggle_command_mode ();
  } else
  if (keypressedd (k_insert, sc0, sc1)) up(1); else
  if (keypressedd (k_delete, sc0, sc1)) down(1); else
  if (keypressed (k_home)) home (); else
  if (keypressed (k_end)) end(); else
  if (keypressedd (k_pgup, pg0, pg1)) pgup(); else
  if (keypressedd (k_pgdn, pg0, pg1)) pgdn(); else
  if (keypressedd (k_backspace)) {
    if (command_mode) del (); else clear();
  } else
  if (keypressed (k_backquote) && !command_mode) {
		roll_unroll (!rollup);
	}
		
  return ret;

}

void console::roll_unroll (int what) {
	rollup = what;
	last ();
}

console& console::operator() (const string& cmd) {
  extern tcl_interp interpreter;
  into_lines (interpreter(cmd).result, *this);
  return *this;
}

void console::toggle_command_mode () {
	command_mode = !command_mode;
	accept_keypress (command_mode);
	last ();
}
